﻿using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System.Linq;

//---------------------------------------------------------------------------------------------------
//UI LifeCircle: OnUIInit[arg] -> OnUIOpen[arg] -> [RunOpenAnimation] -> OnUIBringToFront -> [Loop] -> [RunCloseAnimation] -> OnUIClose
//                                                                                    |
//                                                                  OnUIResume        ->        OnUIHide
//                                                                       |                         |
//                                                                       |            <-           |
//                                                                       |     OnUIBringToFront    |
//
//特殊UI事件说明
//OnPreLoad:         ui预加载接口，当调用PreLoadUI时候触发
//OnUIRefresh[arg]:  ui刷新接口，仅仅刷新ui数据
//OnUIBringToFront:  ui被置顶接口，当它上层ui隐藏或者关闭时候调用
//OnCustom:          ui自定义接口，调用当前显示在最上层的ui
//
//RunOpenAnimation & RunCloseAnimation: UI动画需要实现shaco.IUIAnimation接口，否则没有UI动画流程
//---------------------------------------------------------------------------------------------------

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2021 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco
{
    [DisallowMultipleComponent]
    public partial class UIRootComponent : MonoBehaviour, IUIRootComponent
    {
        [SerializeField]
        private partial class UIState : MonoBehaviour, IUIState
        {
            public bool isLoading { get { return _isLoading; } }
            public bool isDestroyed { get { return _isDestroyed; } }
            public string key { get { return _key; } }
            public System.Type uiType { get { return _uiType; } }
            public GameObject parent { get { return _parent; } }
            public System.Collections.ObjectModel.ReadOnlyCollection<UIPrefab> uiPrefabs { get { return _uiPrefabs.AsReadOnly(); } }
            public UIRootComponent uiRoot { get { return _uiRoot; } }
            public UIEvent uiEvent { get { return _uiEvent; } }
            public UIPrefab firstUIPrefab
            {
                get
                {
                    if (!IsValid() || null == _uiPrefabs || 0 == _uiPrefabs.Count)
                    {
                        return new UIPrefab();
                    }
                    else
                        return _uiPrefabs[0];
                }
            }

            public string _key = null;
            public System.Type _uiType;
            public GameObject _parent = null;
            [SerializeField]
            public List<UIPrefab> _uiPrefabs = new List<UIPrefab>();
            public UIRootComponent _uiRoot = null;
            public UIEvent _uiEvent = new UIEvent();
            public bool _isDestroyed = false;

            private bool _isLoading = false;

            public void SetLoading(bool value)
            {
                this._isLoading = value;
            }

            public bool IsValid(bool isPrintError = true)
            {
                if (_isDestroyed)
                {
                    if (isPrintError)
                        shaco.Log.Error("UIRootComponent IsValid error: is destroyed, key=" + _key, _parent);
                    return false;
                }

                if (_isLoading)
                {
                    if (isPrintError)
                        shaco.Log.Error("UIRootComponent IsValid error: is loading, key=" + _key, _parent);
                    return false;
                }

                if (null == parent)
                {
                    if (isPrintError)
                        shaco.Log.Error("UIRootComponent IsValid error: parent is null, key=" + _key, _parent);
                    return false;
                }
                return true;
            }

            public void RemovePrefabAtIndex(int index)
            {
                if (index < 0 || index > _uiPrefabs.Count - 1)
                {
                    Log.Error("UIRootComponent RemovePrefabAtIndex error: out of range, index=" + index + " count=" + _uiPrefabs.Count);
                    return;
                }
                _uiPrefabs.RemoveAt(index);
            }
        }

        private class UIPreLoadInfo
        {
            public UIState uiState = null;
            public List<UIPrefab> uiPrefabs = new List<UIPrefab>();
        }
        public int layerIndex
        {
            get { return _layerIndex; }
        }

        public int Count
        {
            get { return _uiDictionary.Count; }
        }

        public int loadingUICount { get { return _loadingUICount; } }

        public string uiName
        {
            get { return this.gameObject.name; }
            set { this.gameObject.name = value; }
        }

        public bool isActived
        {
            get { return this.gameObject.activeInHierarchy; }
        }

        public float openUITimeoutSeconds
        {
            get { return _openUITimeoutSeconds; }
            set { _openUITimeoutSeconds = value; }
        }

        public class OpenUIResult
        {
            public UIPrefab prefab;
        }

        [SerializeField]
        private int _layerIndex = 0;

        private Dictionary<string, UIState> _uiDictionary = new Dictionary<string, UIState>();
        private Dictionary<string, UIPreLoadInfo> _uiPreLoadCache = new Dictionary<string, UIPreLoadInfo>();
        private Dictionary<System.Type, bool> _uiAttributesOn = new Dictionary<System.Type, bool>();
        private GameObject _uiPreLoadCacheParent = null;
        private int _loadingUICount = 0;
        private float _openUITimeoutSeconds = 1.0f;

        //ui动画委托名字
        static private readonly string INTERFACE_NAME_UI_ANIMATION = typeof(IUIAnimation).FullName;

        private bool _isDestroyed = false;

        void Awake()
        {
            GameHelper.ui.AddUIRootComponent(this);
        }

        void OnDestroy()
        {
            foreach (var iter in _uiDictionary)
            {
                CheckAutoUnloadAsset(iter.Value);
            }
            _isDestroyed = true;
            GameHelper.ui.RemoveUIRootComponent(this);
        }

        public T OpenUI<T>(shaco.Base.BaseEventArg arg = null) where T : UnityEngine.Component
        {
            var result = new OpenUIResult();
            StartCoroutine(OpenUIBase<T>(arg, result));
            var uiObj = result.prefab?.prefab;
            return uiObj == null ? null : uiObj.GetComponent<T>();
        }

        IEnumerator OpenUIBase<T>(shaco.Base.BaseEventArg arg, OpenUIResult result) where T : UnityEngine.Component
        {
            var key = shaco.Base.Utility.ToTypeString<T>();
            UIState uiState = null;

            if (_uiDictionary.ContainsKey(key))
            {
                uiState = (UIState)_uiDictionary[key];
                uiState.uiEvent.GetOnUIOpenStatck();
                yield return OnOpenUIResume<T>(uiState, key, arg, result);
            }
            else
            {
                uiState = CreateUIStateFromLoadOrCahce(key, typeof(T));
                uiState.uiEvent.GetOnUIOpenStatck();
                AddUIState(uiState);

                uiState.uiEvent.GetOnUIInitStatck();
                yield return CreateUIPrefabFromLoadOrCache<T>(uiState, result, arg);
            }

            shaco.GameHelper.uiDepth.ChangeDepthAsTopDisplay(this, uiState);
        }

        public void HideUI<T>() where T : UnityEngine.Component
        {
            HideUI(typeof(T));
        }
        private void HideUI(System.Type uiType)
        {
            var uiState = GetUIState(uiType);
            if (uiState == null)
            {
                Log.Error("UIRootComponent HideUI error: ui is missing, key=" + uiType.ToTypeString(), this);
                return;
            }
            HideUIBase((UIState)uiState, true);
        }

        public void CloseUI<T>() where T : UnityEngine.Component
        {
            CloseUI(typeof(T));
        }
        private void CloseUI(System.Type uiType)
        {
            var uiState = GetUIState(uiType);
            if (uiState == null)
            {
                Log.Error("UIRootComponent CloseUI error: ui is missing, key=" + uiType.ToTypeString(), this);
                return;
            }
            CloseUIBase((UIState)uiState);
        }

        public void RefreshUI<T>(shaco.Base.BaseEventArg arg = null) where T : UnityEngine.Component
        {
            var uiState = GetUIState(typeof(T));
            if (uiState == null)
            {
                Log.Error("UIRootComponent RefreshUI error: ui is missing, key=" + shaco.Base.Utility.ToTypeString<T>(), this);
                return;
            }

            for (int i = 0; i < uiState.uiPrefabs.Count; ++i)
            {
                RefreshTargetUIBase(uiState, uiState.uiPrefabs[i], arg);
            }
        }

        /// <summary>
        /// 将UI显示到最上层
        /// </summary>
        public void BringToFront<T>() where T : UnityEngine.Component
        {
            BringToFront(typeof(T));
        }
        private void BringToFront(System.Type uiType)
        {
            var key = uiType.ToTypeString();

            UIState uiState = null;

            if (_uiDictionary.ContainsKey(key))
            {
                uiState = (UIState)_uiDictionary[key];
                shaco.GameHelper.uiDepth.ChangeDepthAsTopDisplay(this, uiState);
                NotifyBringToFrontUI();
            }
            else
            {
                shaco.Log.Error("UIRootComponent BringToFront error: not found ui=" + key, this);
            }
        }

        public void PreLoadUI<T>(int preloadCount = 1) where T : UnityEngine.Component
        {
            var key = shaco.Base.Utility.ToTypeString<T>();

            //check parent
            if (null == _uiPreLoadCacheParent)
            {
                _uiPreLoadCacheParent = new GameObject("UIPrefabPreLoadCache");
                _uiPreLoadCacheParent.transform.SetParent(this.transform, false);
                _uiPreLoadCacheParent.transform.SetAsFirstSibling();
            }

            //check ui prefab
            for (int i = 0; i < preloadCount; ++i)
            {
                StartCoroutine(PreLoadUIBase<T>(key));
            }
        }

        IEnumerator PreLoadUIBase<T>(string key) where T : UnityEngine.Component
        {
            var result = new OpenUIResult();

            //check ui state
            if (!_uiPreLoadCache.TryGetValue(key, out var uiPreLoadFind))
            {
                uiPreLoadFind = new UIPreLoadInfo();
                uiPreLoadFind.uiState = CreateUIState(key, typeof(T));
                uiPreLoadFind.uiState.gameObject.SetActive(false);
                uiPreLoadFind.uiState.parent.transform.SetParent(_uiPreLoadCacheParent.transform, false);
            }
            yield return CreateUIPrefab<T>(uiPreLoadFind.uiState, result, null, true);

            var loadPrefab = result.prefab;
            if (null != loadPrefab && null != loadPrefab.prefab)
            {
                _uiPreLoadCache.Add(key, uiPreLoadFind);
                uiPreLoadFind.uiPrefabs.Add(loadPrefab);

                loadPrefab.prefab.SetActive(false);
                MonoBehaviour.DontDestroyOnLoad(loadPrefab.prefab);
                loadPrefab.prefab.transform.SetParent(_uiPreLoadCacheParent.transform, false);
                uiPreLoadFind.uiState.uiEvent.DispatchEvent(key, loadPrefab, null, UIEvent.EventType.OnPreLoad);
            }
        }

        public int GetPreLoadUICount<T>() where T : UnityEngine.Component
        {
            var key = shaco.Base.Utility.ToTypeString<T>();
            UIPreLoadInfo uiPreLoadFind = null;
            _uiPreLoadCache.TryGetValue(key, out uiPreLoadFind);
            return uiPreLoadFind == null ? 0 : uiPreLoadFind.uiPrefabs.Count;
        }

        public IUIState PopupUIAndHide(params System.Type[] igoreUIs)
        {
            UIState retValue = PopupUI(true, igoreUIs);
            if (null == retValue)
            {
                shaco.Log.Warning("UIRootComponent PopupUIAndHide erorr: not found active ui", this);
                return retValue;
            }

            HideUIBase(retValue, true);
            return retValue;
        }

        public IUIState PopupUIAndClose(bool onlyActivedUI, params System.Type[] igoreUIs)
        {
            UIState retValue = PopupUI(onlyActivedUI, igoreUIs);
            if (null == retValue)
            {
                shaco.Log.Warning("UIRootComponent PopupUIAndClose erorr: not found ui, onlyActivedUI=" + onlyActivedUI, this);
                return retValue;
            }

            CloseUIBase(retValue);
            return retValue;
        }

        /// <summary>
        /// 恢复ui根节点下所有ui显示
        /// <param name="layerIndex">UI根节点下标</param>
        /// </summary>
        public void ResumeAllUI()
        {
            foreach (var iter in _uiDictionary)
            {
                var uiState = iter.Value;
                if (uiState.IsValid())
                {
                    for (int i = 0; i < uiState.uiPrefabs.Count; ++i)
                    {
                        // var prefabTmp = uiState.uiPrefabs[i].prefab;
                        var uiPrefabTmp = uiState.uiPrefabs[i];
                        uiState.uiEvent.DispatchEvent(uiState, null, UIEvent.EventType.OnResume);
                        CheckUIOpenAnimation(uiState, uiPrefabTmp);
                    }
                }
            }
        }

        /// <summary>
        /// 隐藏ui根节点下所有ui
        /// <param name="layerIndex">UI根节点下标</param>
        /// </summary>
        public void HideAllUI()
        {
            foreach (var iter in _uiDictionary)
            {
                HideUIBase(iter.Value, true);
            }
        }

        /// <summary>
        /// 关闭ui根节点下所有ui
        /// <param name="layerIndex">UI根节点下标</param>
        /// </summary>
        public void CloseAllUI()
        {
            foreach (var iter in _uiDictionary)
            {
                CloseUIBase(iter.Value);
            }
        }

        public UnityEngine.GameObject GetTopUI(bool onlyActivedUI)
        {
            UnityEngine.GameObject retValue = null;
            var findUI = GetTopUIBase(onlyActivedUI);
            if (null == findUI || findUI.uiPrefabs.IsNullOrEmpty())
            {
                return null;
            }

            if (onlyActivedUI)
            {
                for (int i = findUI.uiPrefabs.Count - 1; i >= 0; --i)
                {
                    var topUIPrefab = findUI.uiPrefabs[i];
                    if (topUIPrefab.prefab.activeInHierarchy)
                    {
                        retValue = topUIPrefab.prefab;
                        break;
                    }
                }
            }
            else if (!findUI.uiPrefabs.IsNullOrEmpty())
            {
                retValue = findUI.uiPrefabs[findUI.uiPrefabs.Count - 1].prefab;
            }

            if (null == retValue)
            {
                Log.Error("UIRootComponent GetTopUI error: not found top ui, onlyActivedUI=" + onlyActivedUI);
            }
            return retValue;
        }

        public IUIState GetUIState(System.Type uiType)
        {
            var key = uiType.FullName;
            return GetUIState(key);
        }

        public IUIState[] GetAllUIState()
        {
            return _uiDictionary.Values.ToArray();
        }

        public void Foreach(System.Func<IUIState, bool> callback)
        {
            foreach (var iter in _uiDictionary)
            {
                var result = true;
                try
                {
                    result = callback(iter.Value);
                }
                catch (System.Exception e)
                {
                    shaco.Log.Error("UIRootComponent Foreach exception: key=" + iter.Value.key + " e=" + e, this);
                }

                if (!result)
                {
                    break;
                }
            }
        }

        public void ClearUI()
        {
            foreach (var iter in _uiDictionary)
            {
                Destroy(iter.Value.parent.gameObject);
            }
            _uiDictionary.Clear();
        }

        public void SetLayerAttributeOn(System.Type attributeType, bool isOn)
        {
            if (!isOn)
            {
                if (!_uiAttributesOn.ContainsKey(attributeType))
                    _uiAttributesOn.Add(attributeType, false);
            }
            else
            {
                if (_uiAttributesOn.ContainsKey(attributeType))
                    _uiAttributesOn.Remove(attributeType);
            }
        }

        private UIState GetUIState(string key)
        {
            return _uiDictionary.ContainsKey(key) ? _uiDictionary[key] : null;
        }

        private UIState PopupUI(bool onlyActivedUI, params System.Type[] igoreUI)
        {
            UIState retValue = null;
            var childount = this.transform.childCount;
            for (int i = childount - 1; i >= 0; --i)
            {
                var uiStateTarget = this.transform.GetChild(i);
                if (!onlyActivedUI || (onlyActivedUI && uiStateTarget.gameObject.activeInHierarchy))
                {
                    bool isIgnoreUI = false;
                    var uiStateTmp = uiStateTarget.GetComponent<UIState>();

                    if (null != uiStateTmp)
                    {
                        if (!uiStateTmp.IsValid(false))
                        {
                            isIgnoreUI = true;
                        }
                        else
                        {
                            for (int j = igoreUI.Length - 1; j >= 0; --j)
                            {
                                if (uiStateTmp.key == igoreUI[j].ToTypeString())
                                {
                                    isIgnoreUI = true;
                                    break;
                                }
                            }
                        }
                    }

                    if (!isIgnoreUI)
                    {
                        retValue = uiStateTmp;
                        break;
                    }
                }
            }
            return retValue;
        }

        private UIPrefab ResumeUIBaseWithFullScreen(UIState uiState)
        {
            var topUI = GetFullScreenUI(uiState);
            CheckFullScreenUIAutoHide(uiState, topUI);
            return ResumeUIBase((UIState)uiState);
        }

        private UIPrefab ResumeUIBase(UIState uiState)
        {
            UIPrefab retValue = null;

            if (uiState.IsValid())
            {
                retValue = uiState.firstUIPrefab;
                uiState.uiEvent.DispatchEvent(uiState, null, UIEvent.EventType.OnResume);
                CheckUIOpenAnimation(uiState, uiState.firstUIPrefab);
            }
            else
            {
                Log.Error("UIRootComponnet ResumeUIBase erorr: ui invalid, key=" + uiState.key);
            }
            return retValue;
        }

        private void HideUIBase(UIState uiState, bool shouldCheckAutoResume)
        {
            if (uiState.IsValid())
            {
                for (int i = 0; i < uiState.uiPrefabs.Count; ++i)
                {
                    // var prefabTmp = uiState.uiPrefabs[i].prefab;
                    var uiPrefabTmp = uiState.uiPrefabs[i];
                    CheckUICloseAnimation(uiState, uiPrefabTmp, false);
                }

                bool isTopUI = GetTopUIBase(true) == (IUIState)uiState;
                var topUI = GetFullScreenUI(uiState);

                if (shouldCheckAutoResume)
                    CheckFullScreenUIAutoResume(uiState, topUI);

                uiState.uiEvent.DispatchEvent(uiState, null, UIEvent.EventType.OnHide);

                //通知被显示到最上层的ui界面
                if (isTopUI)
                    NotifyBringToFrontUI();
            }
        }

        private IEnumerator OnOpenUIResume<T>(UIState uiState, string key, shaco.Base.BaseEventArg arg, OpenUIResult result) where T : UnityEngine.Component
        {
            //允许重复打开的UI
            if (IsDefinedAttribute(typeof(T), typeof(UILayerAllowDuplicateAttribute)))
            {
                yield return OpenDuplicateUI<T>(uiState, key, arg, result);
            }
            //如果资源还在加载，则等待加载完毕
            else if (uiState.IsValid(false))
            {
                result.prefab = ResumeUIBaseWithFullScreen(uiState);
            }
        }

        private UIState GetFullScreenUI(IUIState uiState)
        {
            if (null != uiState && !IsDefinedAttribute(uiState, typeof(UILayerFullScreenAttribute)))
                return null;

            var topUI = shaco.GameHelper.uiDepth.GetTopUI(this, v =>
            {
                if (uiState == v)
                    return false;

                var stateTmp = (UIState)v;
                if (stateTmp.isDestroyed)
                    return false;

                if (IsDefinedAttribute(v, typeof(UILayerFullScreenAttribute)))
                    return true;
                else
                    return false;
            });
            return topUI as UIState;
        }

        private void CheckFullScreenUIAutoHide(IUIState uiState, IUIState topUI)
        {
            if (null == topUI || uiState == topUI)
                return;

            //如果该界面已经被关闭了，则不再执行自动隐藏
            if (!_uiDictionary.ContainsKey(topUI.key))
            {
                return;
            }

            HideUIBase((UIState)topUI, false);
        }

        private void CheckFullScreenUIAutoResume(UIState uiState, UIState topUI)
        {
            if (null == uiState || !IsDefinedAttribute(uiState, typeof(UILayerFullScreenAttribute)))
                return;

            if (null == topUI)
                return;

            StartCoroutine(CheckFullScreenUIAutoResumeBase(uiState, topUI));
        }

        private IEnumerator CheckFullScreenUIAutoResumeBase(UIState uiState, UIState topUI)
        {
            if (null == topUI)
                yield break;

            //等待可能正在加载新的ui
            while (topUI.isLoading)
            {
                yield return null;
            }

            if (topUI.IsValid())
            {
                ResumeUIBase(topUI);
            }
            else
            {
                var otherTopUI = GetFullScreenUI(topUI);
                yield return CheckFullScreenUIAutoResumeBase(topUI, otherTopUI);
            }
        }

        private void CloseUIBase(UIState uiState)
        {
            if (null == uiState) return;

            var shouldRemovePrefabs = new Queue<GameObject>();
            for (int i = 0; i < uiState.uiPrefabs.Count; ++i)
            {
                var prefabTmp = uiState.uiPrefabs[i].prefab;
                var uiPrefabTmp = uiState.uiPrefabs[i];
                shouldRemovePrefabs.Enqueue(prefabTmp);
                CheckUICloseAnimation(uiState, uiPrefabTmp, true);
            }

            //等待所有ui动画执行完毕再统一移除它们
            bool isTopUI = GetTopUIBase(true) == (IUIState)uiState;
            var topUI = GetFullScreenUI(uiState);

            CheckFullScreenUIAutoResume(uiState, topUI);

            uiState.uiEvent.DispatchEvent(uiState, null, UIEvent.EventType.OnClose);
            RemoveUI(uiState, shouldRemovePrefabs);

            //通知被显示到最上层的ui界面
            if (isTopUI)
                NotifyBringToFrontUI();
        }

        private void CheckAutoUnloadAsset(IUIState uiState)
        {
            var uiPrefabPath = GetUIPrefabPath(uiState.key, uiState.uiType);
            if (!string.IsNullOrEmpty(uiPrefabPath))
            {
                foreach (var iter in uiState.uiPrefabs)
                    GameHelper.res.Release(iter.resourceRequest);
            }
        }

        private void NotifyBringToFrontUI()
        {
            var topUI = GetTopUIBase(true);
            if (null != topUI)
            {
                topUI.uiEvent.DispatchEvent(topUI, null, UIEvent.EventType.OnBringToFront);
                // var uiType = shaco.Base.Utility.Assembly.GetTypeWithinLoadedAssemblies(topUI.key);
            }
        }

        private string GetUIPrefabPath(string key, System.Type type)
        {
            string retValue = null;
            var attributeTmp = type.GetAttribute<UILayerCustomPrefabPathAttribute>();
            if (null == attributeTmp)
            {
                retValue = GameHelper.uiConfig.GetFullPath(key);
            }
            else
            {
                retValue = attributeTmp.customPrefabPath;
            }
            return retValue;
        }

        private UIState CreateUIStateFromLoadOrCahce(string key, System.Type type)
        {
            UIState retValue = (UIState)GetUIState(key);

            if (null == retValue)
            {
                UIPreLoadInfo uiPreLoadFind = null;
                if (!_uiPreLoadCache.TryGetValue(key, out uiPreLoadFind))
                {
                    retValue = CreateUIState(key, type);
                }
                else
                {
                    retValue = uiPreLoadFind.uiState;
                }
            }
            else
            {
                shaco.Log.Info("UIRootComponent CreateUIStateFromLoadOrCahce error: has created ui, key=" + key, retValue.parent);
            }

            retValue._isDestroyed = false;
            return retValue;
        }

        private UIState CreateUIState(string key, System.Type uiType)
        {
            UIState retValue = null;
            var parentNew = new GameObject();
            retValue = parentNew.AddComponent<UIState>();

            parentNew.name = key;
            retValue._key = key;
            retValue._uiType = uiType;
            retValue._parent = parentNew;
            retValue._uiRoot = this;
            retValue.parent.transform.SetParent(this.transform, false);

            //for UGUI
            //如果父节点设置了RectTransform则和父节点的RectTrasnform保持一致
            var rootRectTransform = this.GetComponent<RectTransform>();
            if (null != rootRectTransform)
            {
                var parentRectTransform = retValue._parent.AddComponent<RectTransform>();
                parentRectTransform.sizeDelta = rootRectTransform.sizeDelta;
                parentRectTransform.anchoredPosition = rootRectTransform.anchoredPosition;
                parentRectTransform.anchorMin = rootRectTransform.anchorMin;
                parentRectTransform.anchorMax = rootRectTransform.anchorMax;
                parentRectTransform.pivot = rootRectTransform.pivot;
                parentRectTransform.localPosition = Vector3.zero;
            }

            return retValue;
        }

        private bool ChangeUIToScene(IUIState uiState, shaco.Base.BaseEventArg arg)
        {
            if (!uiState.uiEvent.DispatchEvent(uiState, arg, UIEvent.EventType.OnInit))
                return false;

            uiState.parent.transform.SetParent(this.transform, false);
            bool hadValidUI = false;

            for (int i = uiState.uiPrefabs.Count - 1; i >= 0; --i)
            {
                if (null != uiState.uiPrefabs[i].prefab)
                {
                    uiState.uiPrefabs[i].prefab.transform.SetParent(uiState.parent.transform, false);
                    hadValidUI = true;
                }
            }
            uiState.parent.layer = this.gameObject.layer;
            return hadValidUI;
        }

        private IEnumerator CreateUIPrefabFromLoadOrCache<T>(UIState uiState, OpenUIResult result, shaco.Base.BaseEventArg arg) where T : UnityEngine.Component
        {
            if (_uiPreLoadCache.ContainsKey(uiState.key))
            {
                var uiCacheTmp = _uiPreLoadCache[uiState.key];

                if (uiCacheTmp.uiPrefabs.IsNullOrEmpty())
                {
                    shaco.Log.Error("UIRootCompnent CreateUIPrefabFromLoadOrCache error: not found prefab in cache, key=" + uiState.key, uiState.parent);
                    yield break;
                }

                var retValue = uiCacheTmp.uiPrefabs[uiCacheTmp.uiPrefabs.Count - 1];
                uiCacheTmp.uiPrefabs.RemoveAt(uiCacheTmp.uiPrefabs.Count - 1);

                result.prefab = retValue;

                var requestLoadAsync = IsDefinedAttribute(typeof(T), typeof(shaco.UILayerOpenAsyncAttribute));
                if (requestLoadAsync && shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OnUIOpenEndEvent<T>>())
                {
                    this.InvokeEvent(new shaco.UIStateChangedEvents.OnUIOpenEndEvent<T>()
                    {
                        uiTarget = null == retValue || null == retValue.prefab ? null : retValue.prefab.GetComponent<T>()
                    });
                }
            }
            else
            {
                yield return CreateUIPrefab<T>(uiState, result, arg, false);
            }
        }

        private IEnumerator CreateUIPrefab<T>(UIState uiState, OpenUIResult result, shaco.Base.BaseEventArg arg, bool isPreload) where T : UnityEngine.Component
        {
            var retValue = new UIPrefab();
            var pathTmp = GetUIPrefabPath(uiState.key, typeof(T));
            var requestLoadAsync = IsDefinedAttribute(typeof(T), typeof(shaco.UILayerOpenAsyncAttribute));
            ActionBase timeoutCheckAction = null;

            uiState.SetLoading(true);

            if (requestLoadAsync)
            {
                var haveInvokedTimeoutEvent = false;
                shaco.UIStateChangedEvents.OpenAysncUIProgressEvent openAsyncUIProgressEventArg = null;
                if (shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OpenAysncUIProgressEvent>())
                {
                    openAsyncUIProgressEventArg = new shaco.UIStateChangedEvents.OpenAysncUIProgressEvent();
                    openAsyncUIProgressEventArg.uiKey = uiState.key;
                }
                if (shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OpenUITimeoutStartEvent>())
                {
                    var customTimeoutSecondsAttribute = typeof(T).GetAttribute(typeof(shaco.UILayerCustomOpenTimeoutSecondsAttribute)) as shaco.UILayerCustomOpenTimeoutSecondsAttribute;
                    var timeoutSeconds = null == customTimeoutSecondsAttribute ? openUITimeoutSeconds : customTimeoutSecondsAttribute.seconds;
                    timeoutCheckAction = shaco.Delay.Run(() =>
                    {
                        haveInvokedTimeoutEvent = true;
                        timeoutCheckAction = null;
                        this.InvokeEvent(new shaco.UIStateChangedEvents.OpenUITimeoutStartEvent() { uiKey = uiState.key });
                    }, timeoutSeconds);
                }

                ++_loadingUICount;
                var loadRequest = GameHelper.res.LoadAsync<GameObject>(pathTmp);
                while (!loadRequest.isDone)
                {
                    if (null != openAsyncUIProgressEventArg && shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OpenAysncUIProgressEvent>())
                    {
                        openAsyncUIProgressEventArg.percent = loadRequest.progress;
                        this.InvokeEvent(openAsyncUIProgressEventArg);
                    }
                    else
                    {
                        //当事件丢失时候置空消息
                        openAsyncUIProgressEventArg = null;
                    }
                    yield return null;
                }
                if (null != openAsyncUIProgressEventArg && openAsyncUIProgressEventArg.percent != 1.0f)
                {
                    openAsyncUIProgressEventArg.percent = 1.0f;
                    this.InvokeEvent(openAsyncUIProgressEventArg);
                }

                retValue.resourceRequest = loadRequest;
                var loadObj = (loadRequest as Base.IResourceHandle).assetObject as GameObject;
                yield return OnUILoadAsyncEnd<T>(retValue, uiState, loadObj, haveInvokedTimeoutEvent, timeoutCheckAction, result);
            }
            else
            {
                var loadRequest = GameHelper.res.Load<GameObject>(pathTmp);
                if (null != loadRequest)
                {
                    var componets = ((GameObject)MonoBehaviour.Instantiate(loadRequest.assetObject as GameObject)).GetComponents<UnityEngine.Component>();
                    retValue.SetComponents(componets);
                }

                retValue.resourceRequest = loadRequest;
                result.prefab = retValue;

                if (shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OnUIOpenEndEvent<T>>())
                {
                    this.InvokeEvent(new shaco.UIStateChangedEvents.OnUIOpenEndEvent<T>()
                    {
                        uiTarget = null == loadRequest ? null : (loadRequest.assetObject as GameObject).GetComponent<T>()
                    });
                }
            }

            if (!isPreload)
            {
                OnNewUIOpen(uiState, arg, result);
            }

            uiState.SetLoading(false);
        }

        void OnNewUIOpen(UIState uiState, shaco.Base.BaseEventArg arg, OpenUIResult result)
        {
            var topUI = GetFullScreenUI(uiState);
            var isDuplicateOpenUI = false;
            var setupUISuccess = true;
            var loadPrefab = result.prefab;

            uiState._uiPrefabs.Add(result.prefab);

            if (_uiDictionary.TryGetValue(uiState.key, out var findValue))
                isDuplicateOpenUI = findValue.uiPrefabs.Count >= 1;

            if (isDuplicateOpenUI)
            {
                setupUISuccess = true;
                ChangeUITargetToScene(uiState, loadPrefab, arg, uiState.parent);
            }
            else
                setupUISuccess = ChangeUIToScene(uiState, arg);

            if (setupUISuccess)
            {
                uiState.uiEvent.DispatchEvent(uiState.key, loadPrefab, arg, UIEvent.EventType.OnOpen);
                CheckFullScreenUIAutoHide(uiState, topUI);
                CheckUIOpenAnimation(uiState, loadPrefab);
            }
            else
            {
                Log.Error("UIRootCompnent OpenUI error: can't change ui to scene, key=" + uiState.key, this);
            }
        }

        private IEnumerator OnUILoadAsyncEnd<T>(UIPrefab uiPrefab, UIState uiState,
                                        GameObject loadObj, bool haveInvokedTimeoutEvent, ActionBase timeoutCheckAction, OpenUIResult result) where T : UnityEngine.Component
        {
            --_loadingUICount;

            //关闭可能还在运行的超时检查
            if (null != timeoutCheckAction)
            {
                timeoutCheckAction.StopMe();
                timeoutCheckAction = null;
            }

            //资源加载完毕，表示超时检查也结束了
            if (haveInvokedTimeoutEvent && shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OpenUITimeoutEndEvent>())
            {
                this.InvokeEvent(new shaco.UIStateChangedEvents.OpenUITimeoutEndEvent() { uiKey = uiState.key });
            }

            //如果资源异步加载完毕后，但是UI根结点已经被销毁的情况下，不执行后续逻辑
            if (_isDestroyed)
            {
                shaco.Log.Error("UIRootCompnent CreateUIPrefab aysnc load ui error: UIRootComponent is already destroyed, key=" + uiState.key, this);
                yield break;
            }

            if (null != loadObj)
            {
                var componets = ((GameObject)MonoBehaviour.Instantiate(loadObj)).GetComponents<UnityEngine.Component>();
                uiPrefab.SetComponents(componets);
            }

            bool canRealoadUI = false;
            shaco.UIStateChangedEvents.OpenAysncUIErrorEvent openAysncUIErrorEvent = null;
            if (null == loadObj)
            {
                if (shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OpenAysncUIErrorEvent>())
                {
                    openAysncUIErrorEvent = new shaco.UIStateChangedEvents.OpenAysncUIErrorEvent();
                    openAysncUIErrorEvent.uiKey = uiState.key;
                    this.InvokeEvent(openAysncUIErrorEvent);
                    canRealoadUI = true;
                }
            }

            //只有界面加载失败和外部要求重新加载情况下才重新加载
            if (canRealoadUI && null != openAysncUIErrorEvent)
            {
                openAysncUIErrorEvent.requestReloadUI = () =>
                {
                    CreateUIPrefab<T>(uiState, result, null, true);
                };
            }
            //加载成功了则直接回调
            else
            {
                result.prefab = uiPrefab;
            }

            if (shaco.GameHelper.Event.HasEventID<shaco.UIStateChangedEvents.OnUIOpenEndEvent<T>>())
            {
                this.InvokeEvent(new shaco.UIStateChangedEvents.OnUIOpenEndEvent<T>()
                {
                    uiTarget = null == loadObj ? null : loadObj.GetComponent<T>()
                });
            }
        }

        private bool AddUIState(UIState uiState)
        {
            if (_uiDictionary.ContainsKey(uiState.key))
            {
                Log.Error("UIRootComponent AddUI error: the ui has been added, key=" + uiState.key, uiState.parent);
                return false;
            }
            _uiDictionary.Add(uiState.key, uiState);
            return true;
        }

        private void RemoveUI(IUIState uiState, IEnumerable<GameObject> removePrefabs)
        {
            uiState.uiEvent.RestStackLocations();

            foreach (var iter in removePrefabs)
            {
                var isFound = false;
                for (int i = uiState.uiPrefabs.Count - 1; i >= 0; --i)
                {
                    var prefabTmp = uiState.uiPrefabs[i].prefab;
                    if (iter == prefabTmp)
                    {
                        ((UIState)uiState).RemovePrefabAtIndex(i);
                        isFound = true;
                    }
                }

                if (!isFound)
                    shaco.Log.Error("UIRootComponent RemoveUI error: not found prefab=" + iter, iter);
            }

            if (0 == uiState.uiPrefabs.Count)
            {
                _uiDictionary.Remove(uiState.key);

                CheckAutoUnloadAsset(uiState);

                if (!uiState.IsValid())
                    return;

                var stateTmp = uiState as UIState;
                stateTmp._isDestroyed = true;
            }
        }

        private bool IsDefinedAttribute(IUIState uiState, System.Type attributeType)
        {
            var retValue = (null == uiState || null == uiState.uiType) ? false : uiState.uiType.IsDefined(attributeType, false);
            if (retValue && _uiAttributesOn.ContainsKey(attributeType))
            {
                retValue = _uiAttributesOn[attributeType];
            }
            return retValue;
        }

        private bool IsDefinedAttribute(System.Type uiType, System.Type attributeType)
        {
            var retValue = uiType.IsDefined(attributeType, false);
            if (retValue && _uiAttributesOn.ContainsKey(attributeType))
            {
                retValue = _uiAttributesOn[attributeType];
            }
            return retValue;
        }

        private bool IsDefinedAttribute(UIState uiState, System.Type attributeType)
        {
            if (null == uiState)
                return false;
            return IsDefinedAttribute(uiState.uiType, attributeType);
        }

        private IUIState GetTopUIBase(bool onlyActivedUI)
        {
            if (onlyActivedUI)
                return shaco.GameHelper.uiDepth.GetTopUI(this, v => v.IsValid(false) && HaveAcitveUIInGroup(v));
            else
                return shaco.GameHelper.uiDepth.GetTopUI(this, v => v.IsValid(false));
        }

        private bool HaveAcitveUIInGroup(IUIState uiState)
        {
            for (int i = uiState.uiPrefabs.Count - 1; i >= 0; --i)
            {
                var prefabTmp = uiState.uiPrefabs[i];
                if (null == prefabTmp.coroutineAnimation && prefabTmp.prefab.activeInHierarchy)
                    return true;
            }
            return false;
        }

        private void CheckUIOpenAnimation(UIState uiState, UIPrefab targetPrefab)
        {
            StartCoroutine(CheckUIOpenAnimationCoroutine(uiState, targetPrefab));
        }

        private IEnumerator CheckUIOpenAnimationCoroutine(UIState uiState, UIPrefab targetPrefab)
        {
            var findInterface = uiState.uiType.GetInterface(INTERFACE_NAME_UI_ANIMATION);
            IEnumerator openAnimation = null;
            IUIAnimation uiAnimation = null;

            if (null == findInterface)
            {
                openAnimation = DefaultOpenAnimation(uiState, targetPrefab);
            }
            else
            {
                uiAnimation = targetPrefab.prefab.GetComponent(typeof(IUIAnimation)) as IUIAnimation;
                if (null == uiAnimation)
                {
                    openAnimation = DefaultOpenAnimation(uiState, targetPrefab);
                    Log.Error("UIRootCompnent CheckUIOpenAnimationCoroutine erorr: not found componnet 'IUIAnimation' in target=" + targetPrefab.prefab + " ui type=" + uiState.uiType.ToTypeString(), uiState.parent);
                }
            }

            if (!uiState.isDestroyed)
            {
                if (null == openAnimation)
                    openAnimation = uiAnimation?.RunUIOpenAnimation();

                if (null == openAnimation)
                {
                    Log.Error("UIRootCompnent CheckUIOpenAnimationCoroutine erorr: openAnimation is null in target=" + targetPrefab.prefab + " ui type=" + uiState.uiType.ToTypeString(), uiState.parent);
                    yield break;
                }

                BreakAnimation(targetPrefab, uiAnimation);
                StartCoroutine(targetPrefab.coroutineAnimation = WaitOpenAnimation(targetPrefab, openAnimation));
                while (null != targetPrefab.coroutineAnimation)
                    yield return null;

                uiState.uiEvent.DispatchEvent(uiState.key, targetPrefab, null, UIEvent.EventType.OnBringToFront);
            }
        }

        IEnumerator WaitOpenAnimation(UIPrefab targetPrefab, IEnumerator openAnimation)
        {
            yield return openAnimation;
            targetPrefab.coroutineAnimation = null;
        }

        private void CheckUICloseAnimation(UIState uiState, UIPrefab targetPrefab, bool isDestroy)
        {
            if (!uiState.IsValid())
                return;

            StartCoroutine(CheckUICloseAnimationCoroutine(uiState, targetPrefab, isDestroy));
        }

        private IEnumerator CheckUICloseAnimationCoroutine(UIState uiState, UIPrefab targetPrefab, bool isDestroy)
        {
            var findInterface = uiState.uiType.GetInterface(INTERFACE_NAME_UI_ANIMATION);
            IEnumerator closeAnimation = null;
            IUIAnimation uiAnimation = null;

            if (null == findInterface)
            {
                closeAnimation = DefaultCloseAnimation(uiState, targetPrefab);
            }
            else
            {
                uiAnimation = targetPrefab.prefab.GetComponent(typeof(IUIAnimation)) as IUIAnimation;
                if (null == uiAnimation)
                {
                    closeAnimation = DefaultCloseAnimation(uiState, targetPrefab);
                    Log.Error("UIRootCompnent CheckUICloseAnimationCoroutine erorr: not found componnet 'IUIAnimation' in target=" + targetPrefab.prefab + " ui type=" + uiState.uiType.ToTypeString(), uiState.parent);
                }
            }

            if (!uiState.isDestroyed)
            {
                if (null == closeAnimation)
                    closeAnimation = uiAnimation?.RunUICloseAnimation();

                if (null == closeAnimation)
                {
                    Log.Error("UIRootCompnent CheckUICloseAnimationCoroutine erorr: closeAnimation is null in target=" + targetPrefab.prefab + " ui type=" + uiState.uiType.ToTypeString(), uiState.parent);
                    yield break;
                }

                BreakAnimation(targetPrefab, uiAnimation);
                StartCoroutine(targetPrefab.coroutineAnimation = WaitCloseAnimation(targetPrefab, closeAnimation));
                while (null != targetPrefab.coroutineAnimation)
                    yield return null;

                if (isDestroy)
                    DestroyUI(uiState, targetPrefab);
            }
        }

        void DestroyUI(UIState uiState, UIPrefab targetPrefab)
        {
            var shouldDestroyParent = 1 >= uiState.parent.transform.childCount;
            UnityHelper.SafeDestroy(targetPrefab.prefab.gameObject);
            if (shouldDestroyParent)
                UnityHelper.SafeDestroy(uiState.parent.gameObject);

            //移除预加载对象
            UIPreLoadInfo uiPreLoadFind = null;
            if (_uiPreLoadCache.TryGetValue(uiState.key, out uiPreLoadFind))
            {
                if (0 == uiPreLoadFind.uiPrefabs.Count)
                    shaco.Log.Error("UIRootComponent DestroyUI error: key" + uiState.key + " load ui is empty");

                for (int i = uiPreLoadFind.uiPrefabs.Count - 1; i >= 0; --i)
                {
                    UnityHelper.SafeDestroy(uiPreLoadFind.uiPrefabs[i].prefab);
                }
                uiPreLoadFind.uiPrefabs.Clear();
                _uiPreLoadCache.Remove(uiState.key);
            }
        }

        IEnumerator WaitCloseAnimation(UIPrefab targetPrefab, IEnumerator closeAnimation)
        {
            yield return closeAnimation;
            targetPrefab.coroutineAnimation = null;
        }

        void BreakAnimation(UIPrefab targetPrefab, IUIAnimation animation)
        {
            if (null != targetPrefab.coroutineAnimation)
            {
                StopCoroutine(targetPrefab.coroutineAnimation);
                targetPrefab.coroutineAnimation = null;
                animation?.OnUIAnimationBreak();
            }
        }

        IEnumerator DefaultOpenAnimation(UIState uiState, UIPrefab targetPrefab)
        {
            if (IsDefinedAttribute(uiState.uiType, typeof(UILayerAllowDuplicateAttribute)))
                targetPrefab.prefab.SetActive(true);
            else
                uiState.parent.SetActive(true);
            yield break;
        }

        IEnumerator DefaultCloseAnimation(UIState uiState, UIPrefab targetPrefab)
        {
            if (IsDefinedAttribute(uiState.uiType, typeof(UILayerAllowDuplicateAttribute)))
                targetPrefab.prefab.SetActive(false);
            else
                uiState.parent.SetActive(false);
            yield break;
        }
    }
}